package tool.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import tool.analysis.IJavaEntityAnalyser;
import tool.analysis.IVariableAndParameterAnalyser;
import tool.codescan.mappers.InnerClassMapper;
import tool.csv.CSVCompatible;
import tool.csv.FileWriter;

	/**
	 * @author jwb09119
	 * @date 2013/11/12
	 * 
	 * This class stores all the data collected by various loggers and makes it available to other
	 * parts of the system.  This is a bit data-class-y, but has enough responsibilities to
	 * justify a separate storage class.
	 * 
	 * @date 2013/11/18
	 * Adding extends CSVCompatible to this class to allow it to export stored data easily to file.
	 * 
	 */

public class ResultStore extends CSVCompatible implements InnerClassMapper{

	private Map<String, InterfaceEntity> interfaceMap;
	private Map<String, AbstractClassEntity> abstractClassMap;
	private Map<String, ConcreteClassEntity> classMap;
	
	private List<FieldRecord> variables;
	private List<ParameterRecord> parameters;
	
	private Map<String, List<TypeDefinition>> innerTypeMap;
	
	public ResultStore (FileWriter writer) {
		
		super(writer);
		
		interfaceMap = new HashMap<>();
		abstractClassMap = new HashMap<>();
		classMap = new HashMap<>();
		variables = new ArrayList<>();
		parameters = new ArrayList<>();
		innerTypeMap = new HashMap<>();
		
	}


	/**
	 * Check that an interfaces exists.
	 * 
	 * @param key - the name of the interface
	 * @return true if the interface exists, otherwise false
	 */
	public boolean interfaceExists (String key) {
		
		return interfaceMap.containsKey(key);
	}

	
	/**
	 * Check that an abstract class exists.
	 * 
	 * @param key - the name of the abstract class
	 * @return true if the abstract class exists, otherwise false
	 */
	public boolean abstractClassExists (String key) {

		return abstractClassMap.containsKey(key);
		
	}
	
	
	/**
	 * Check that a class exists.
	 * 
	 * @param key - the name of the class
	 * @return true if the class exists, otherwise false
	 */
	public boolean classExists (String key) {

		return classMap.containsKey(key);
		
	}

	
	/**
	 * Check that a type is a known interface type.
	 * 
	 * @param type - a String representing the full name of the type to be checked in the format
	 * 				 'package*.Type'
	 * @return - true if the type matches an interface found in the system under analysis,
	 * 		     otherwise false.
	 */
	public boolean checkTypeIsInterface(String type) {

		return interfaceMap.containsKey(type);
		
	}
	
	
	/**
	 * Gets a count of the number of implementers of the specified interface
	 * @param interfaceName
	 * @return int - the number of implementors of the specified interface, note that there is
	 *         no way to distinguish between a non-existed interface and one that is not
	 *         implemented using this method.
	 * @see checkTypeIsInterface(String type) for existence check for an interface name.
	 */
    public int getImplementersCountFor (String interfaceName) {

        int total = 0;
        
        for (ConcreteClassEntity cc : classMap.values()) {
            
            for (TypeDefinition td : cc.getImplementedInterfaces()) {
                
                if (td.getNameWithPath().equals(interfaceName)) { 
                    total++;
                }
                
            }
            
        }
        
        for (AbstractClassEntity ac : abstractClassMap.values()) {
            
            for (TypeDefinition td : ac.getImplementedInterfaces()) {
                
                if (td.getNameWithPath().equals(interfaceName)) { 
                    total++;
                }
                
            }
            
        }
        
        return total;
    }

	
    /**
     * Gets a count of the number of extenders of the specified interface
     * @param interfaceName
     * @return int - the number of extenders of the specified interface, note that there is
     *         no way to distinguish between a non-existed interface and one that is not
     *         implemented using this method.
     * @see checkTypeIsInterface(String type) for existence check for an interface name.
     */
    public int getExtendersCountFor (String interfaceName) {

        int total = 0;
        
        for (InterfaceEntity i : interfaceMap.values()) {
            
            for (TypeDefinition td : i.getExtendedTypes()) {
                
                if (td.getNameWithPath().equals(interfaceName)) {
                    total++;
                }
                
            }
            
        }
        
        return total;
    }
    
	
	/**
	 * Register an interface in the results dataset
	 * @param key - the name of the interface
	 * @param javaEntity - the properties of the interface
	 */
	public void addInterface (String key, InterfaceEntity i) {

		interfaceMap.put(key, i);
		
	}

	
	/**
	 * Register an abstract class in the results dataset
	 * @param key - the name of the abstract class
	 * @param javaEntity - the properties of the abstract class
	 */
	public void addAbstractClass (String key, AbstractClassEntity ac) {
		
		abstractClassMap.put(key, ac);
		
	}


	/**
	 * Register a class in the results dataset
	 * @param key - the name of the class
	 * @param javaEntity - the properties of the class
	 */
	public void addClass (String key, ConcreteClassEntity cc) {
		
		classMap.put(key, cc);
		
	}
	

	/**
	 * Register a variable
	 * @param fr - the FieldRecord
	 */
	public void addVariable(FieldRecord fr) {
		
		variables.add(fr);
		
	}
	
	
	/**
	 * Register a parameter
	 * @param pr - the ParameterRecord
	 */
	public void addParameter(ParameterRecord pr) {
		
		parameters.add(pr);
		
	}
	
	
	/**
	 * Register an inner class
	 * @param ic - the TypeDefinition of the inner class
	 */
    public void addInnerClass (String icName, TypeDefinition containingClass) {

        String containingType = containingClass.getNameWithPath();
        
        TypeDefinition innerClass =  TypeDefinition.createInnerClassDefinition(icName, 
                                             containingClass.getName(), containingClass.getPath());
        
        if (innerTypeMap.containsKey(containingType)) {
            
            innerTypeMap.get(containingType).add(innerClass);
            
        } else {
            
            List<TypeDefinition> ics = new ArrayList<>();
            ics.add(innerClass);
            innerTypeMap.put(containingType, ics);
            
        }
        
    }
    

    /**
     * Gets a deep copy of the list of interface names.
     * @return Set<String> - Containing zero or more Strings
     */
    public Set<String> getAllInterfaceNames () {

        Set<String> reply = new LinkedHashSet<>();
        
        for (String i : interfaceMap.keySet()) {
            
            reply.add(new String(i));
            
        }
        
        return reply;
        
    }
    
    
    public int getConcreteClassCount () {

        return classMap.size();
        
    }



    public int getAbstractClassCount () {

        return abstractClassMap.size();
        
    }



    public int getInterfaceCount () {

        return interfaceMap.size();
        
    }
    

    public int getCountOfAbstractClassImplements () {

        int reply = 0;
        
        for (AbstractClassEntity ac : abstractClassMap.values()) {
            
            reply += ac.getImplementedInterfaces().size();
            
        }
        
        return reply;
        
    }



    public int getCountOfConcreteClassImplements () {

        int reply = 0;
        
        for (ConcreteClassEntity cc : classMap.values()) {
            
            reply += cc.getImplementedInterfaces().size();
            
        }
        
        return reply;
        
    }



    public int getCountOfInterfaceExtends () {

        int reply = 0;
        
        for (InterfaceEntity i : interfaceMap.values()) {
            
            reply += i.getExtendedTypes().size();
            
        }
        
        return reply;
        
    }
    
	
	/**
	 *  Prints a summary of the most recent analysis logged
	 */
	public void printSummary () {
		
		System.out.println("Entities Identified:");
		System.out.println("\tInterfaces: " + interfaceMap.size());
		System.out.println("\tAbstract Classes: " + abstractClassMap.size());
		System.out.println("\tClasses: " + classMap.size());
		System.out.println("Total Entities: " + (interfaceMap.size() 
												+ abstractClassMap.size() 
												+ classMap.size()));
		System.out.println("\nAlso:");
		System.out.println("\tVariables: " + variables.size());
		System.out.println("\tParameters: " + parameters.size());
		
	}

	
	public void printDetailedSummary () {
		
		printSummary();
		System.out.println();
		
		for (Entry<String, InterfaceEntity> i : interfaceMap.entrySet()) {
			
			System.out.println(i.getValue().toString());
			
		}
		
		for (Entry<String, AbstractClassEntity> i : abstractClassMap.entrySet()) {
			
			System.out.println(i.getValue().toString());
			
		}
		
		for (Entry<String, ConcreteClassEntity> i : classMap.entrySet()) {
			
			System.out.println(i.getValue().toString());
			
		}
		
	}


	public void visitJavaEntities (IJavaEntityAnalyser visitor) {
		
		for (JavaEntity entity : interfaceMap.values()) {
			
			entity.accept(visitor);
			
		}
		
		for (JavaEntity entity : abstractClassMap.values()) {
			
			entity.accept(visitor);
			
		}
		
		for (JavaEntity entity : classMap.values()) {
			
			entity.accept(visitor);
			
		}
		
	}
	
	
	public void visitVariablesAndParameters (IVariableAndParameterAnalyser visitor) {
	    
	    for (FieldRecord fr : variables) {
	        
	        fr.accept(visitor);
	        
	    }
	    
	    for (ParameterRecord pr : parameters) {
	        
	        pr.accept(visitor);
	        
	    }
	    
	}
	
	
	/****************************
     * InnerClassMapper Methods *
     ****************************/
	
    @Override
    public boolean checkForInnerClasses (String typeName) {

        return innerTypeMap.containsKey(typeName);
        
    }



    @Override
    public List<TypeDefinition> getInnerClasses (String fulltypeName) {

        List<TypeDefinition> reply;
        
        if (innerTypeMap.containsKey(fulltypeName)) {
            
            reply = innerTypeMap.get(fulltypeName);
            
        } else {
            
            reply = new ArrayList<>();
            
        }
        
        return reply;
        
    }
    
    

    @Override
    public void logInnerClass (TypeDefinition innerClassFullName, 
                                                               TypeDefinition currentVisitedType) {

        if (innerTypeMap.containsKey(currentVisitedType.getNameWithPath())) {
            
            innerTypeMap.get(currentVisitedType.getNameWithPath()).add(innerClassFullName);
            
        } else {
            
            List<TypeDefinition> inners = new ArrayList<>();
            inners.add(innerClassFullName);
            innerTypeMap.put(currentVisitedType.getNameWithPath(), inners);
            
        }
        
    }

    
	/**************************
	 * CSV Compatible Methods *
	 **************************/
	@Override
	protected String getExportHeader() {
		
		return "Contents of application data store";
		
	}


	@Override
	protected void writeStateToCSV(FileWriter writer) {
		
		JavaEntity entity;
		
		writer.writeComment("Name, EntityType, ImplementsCount, ExtendsCount, InterfacesImplemented, TypesExtended");
		
		String name;
		String entityType;
		String interfaceCount;
		String extendsCount;
		String interfaces;
		String extended;
		
		entityType = "INTERFACE";
		for ( Entry<String, InterfaceEntity> entry : interfaceMap.entrySet()) {
			
			entity = entry.getValue();

			name = entity.getName();
			interfaceCount = String.valueOf(entity.getImplementedInterfaces().size());
			extendsCount = String.valueOf(entity.getExtendedTypes().size());
			interfaces = writer.toCSVFormat(entity.getImplementedInterfaces());
			extended = writer.toCSVFormat(entity.getExtendedTypes());

			writer.writeComment(name, entityType, interfaceCount, extendsCount, interfaces, extended);

		}
		
		entityType = "ABSTRACT";
		for ( Entry<String, AbstractClassEntity> entry : abstractClassMap.entrySet()) {
			
			entity = entry.getValue();

			name = entity.getName();
			interfaceCount = String.valueOf(entity.getImplementedInterfaces().size());
			extendsCount = String.valueOf(entity.getExtendedTypes().size());
			interfaces = writer.toCSVFormat(entity.getImplementedInterfaces());
			extended = writer.toCSVFormat(entity.getExtendedTypes());

			writer.writeComment(name, entityType, interfaceCount, extendsCount, interfaces, extended);
			
		}
		
		entityType = "CLASS";
		for ( Entry<String, ConcreteClassEntity> entry : classMap.entrySet()) {
			
			entity = entry.getValue();

			name = entity.getName();
			interfaceCount = String.valueOf(entity.getImplementedInterfaces().size());
			extendsCount = String.valueOf(entity.getExtendedTypes().size());
			interfaces = writer.toCSVFormat(entity.getImplementedInterfaces());
			extended = writer.toCSVFormat(entity.getExtendedTypes());

			writer.writeComment(name, entityType, interfaceCount, extendsCount, interfaces, extended);
			
		}
		
		// TODO - this should probably be dumped somewhere, but not here.  May be an indicator that 
		//        the inner class functionality should be elsewhere.  It is not really gathered
		//        data for analysis, perhaps belongs near/in the type resolver?
//		writer.writeComment("***Listing of recorded inner classes from initial pre-parse pass***");
//		
//		entityType = "INNERCLASS";
//		for (Entry<String, List<TypeDefinition>> entry : innerTypeMap.entrySet()) {
//		    
//		    for (TypeDefinition t : entry.getValue()) {
//		        
//		        writer.writeComment(t.getNameWithPath());
//		        
//		    }
//		    
//		}
		
	}


}

